Skip to main content

Run

Let's cubist start and then run our token bridge!

Start Cubist chains and relayer

Now that we've built our contracts, we want to deploy them and then interact with them. We're going to do this locally; the config (cubist-config.json) already specifies the URLs on which the chains will run. The app config will look something like this:

{
...
"network_profiles": {
"default": {
"ethereum": { "url": "http://127.0.0.1:8545" },
"polygon": { "url": "http://127.0.0.1:9545" }
}
}
}

You can alter the config to specify other URLs if you choose.

To start both the local chains and the Cubist relayer, use:

cubist start

Note that this command may take a while (i.e., a minute) the first time you run it. This is because Cubist needs to download and build the local chain running services before it can start the chains.

Once you've run cubist start, you'll see output that looks something like this:

Launching chains
ethereum ✔ [ 0s] [....................] http://localhost:8545/
polygon ✔ [ 1s] [....................] http://localhost:9545/ All servers available
Watching <path>/cubist-deploy dir

This output shows where the localnets are running (e.g., localhost:8545), and how long they took to become initialized and available. The output also lets us know that the local relayer is successfully watching for the events that tell it to relay information from one chain to another.

tip

Alternatively, you can start the chains and the relayer seperately with cubist chains start and cubist relayer start. For more information on these and other Cubist CLI commands, try cubist help, or check out the CLI reference.

caution

Right now, Avalanche and Avalanche-subnet localnets are slower to start up than other networks. We're working on it!

Run the app

Let's deploy and use our token bridge from within JavaScript or TypeScript.

(Advanced) deployment

First, let's deploy our TokenSender and ERC20Bridged contracts. This is a little more complex than previous examples, since there's a circular dependency between the two contracts: the TokenSender stores a reference to ERC20Bridged, while ERC20Bridged stores a reference to TokenSender. This circular reference is important because it lets the token bridge "go both ways"; we can use it to both buy and sell native tokens and ERC20 tokens.

One way of handling circular dependencies is to deploy one contract, deploy another with the first contract's address, and then update the first contract to include the second contract's address. We don't have to do that here. Instead, we're going to break the circular dependency by explicitly deploying our contracts and their generated shims separately.

This strategy works because the shims don't hold (circular) references to any other contracts; all they do is emit events when they are called. We can take advantage of the shims' simplicity to successfully deploy all of our contracts in sequence---without having to update them later. Here's the strategy we'll use:

  1. Deploy ERC20Bridged's shim (on Ethereum)
  2. Deploy TokenSender to Ethereum (and its shim to Polygon), giving the constructor the address of ERC20Bridged's shim
  3. Deploy the real ERC20Bridged contract to Polygon, giving it the address of the TokenSender shim on Polygon.

The deployment code in deploy.js (or ts) follows this strategy:

// Deploy the ERC20Bridged's shim (Ethereum)
const erc20bShims = await ERC20Bridged.deployShims();
// Get the ERC20Bridged's shim address on Ethereum
const erc20BridgedShim = erc20bShims.get(TokenSender.target());
// Deploy the TokenSender to Ethereum (and its shim to Polygon)
const tokenSender = await TokenSender.deploy(erc20BridgedShim.address);
// Deploy ERC20Bridged (to Polygon) with the TokenSender shim address (on Polygon).
// We already deployed all the shims (in this case only one), so we're going to call
// `deployWithShims` to add the native ERC20Bridged contract to the shim's permitted callers
const erc20Bridged = await ERC20Bridged.deployWithShims(erc20bShims,
"FooBarBaz", "FBB", tokenSender.addressOn(ERC20Bridged.target()));

First it deploys the ERC20Bridged shim to Ethereum, then TokenSender (to Ethereum) and its shim (to Polygon), and finally ERC20Bridged (to Polygon).

Buy

The buy script lets us transfer native (wei) tokens on Ethereum for our FBB tokens on Polygon (with a conversion rate of 99.9%). We specify receiverAccount---a Polygon account for receiving FBB---as input to the buy script.

Let's look at the first few lines of buy. These lines use Cubist to get references to our deployed contracts and the account associated with TokenSender:

// Get new Cubist abstraction specific to this dapp
const cubist = new CubistORM();

// Get Cubist abstraction for the TokenSender contract
const TokenSender = cubist.TokenSender;
// Get Cubist abstraction for the ERC20Bridged contract
const ERC20Bridged = cubist.ERC20Bridged;
// Get chain-specific---in this case, Ethereum---abstraction
// for chain on which the TokenSender (non-shim) contract was deployed
const tsProject = TokenSender.project;

// Get Ethereum address associated with TokenSender
const sendingAccount = await tsProject.getSignerAddress();
// Get deployed TokenSender and ERC20 contract objects
const tokenSender = await TokenSender.deployed();
const erc20Bridged = await ERC20Bridged.deployed();

Next, we're going to use our contracts and the senderAccount and receiverAccount to swap Ethereum tokens for FBB. Recall that TokenSender is deployed on Ethereum and ERC20Bridged is deployed on Polygon:

// Get balance of receiver account on Polygon
const originalReceiverBalance = await erc20Bridged.inner.balanceOf(receivingAccount);
// Send from the TokenSender (on Ethereum) to our receiving account
await (await tokenSender.inner.bridgeSend(receivingAccount, { value: amount })).wait();
while (true) {
const newReceiverBalance = await erc20Bridged.inner.balanceOf(receivingAccount);
// Check if the receiving account has received the tokens
if (originalReceiverBalance.add(amount.sub(fee)).eq(newReceiverBalance)) {
console.log(`Received tokens! New balance: ${newReceiverBalance} fbb`);
break;
}
await sleep(1000);
}

This code snippet uses Cubist-generated binding functions to invoke methods on both the TokenSender and ERC20Bridged smart contracts (e.g., bridgeSend and balanceOf).

note

The script checks if the new balance in the receiving account is the old balance amount minus a few. In a real application, you'd account for concurrent transfers by, e.g., associating a transfer id with each transfer.

sell is similar, but instead swaps in the other direction: FBB for wei. Finally, balances will show a table of balances in our accounts on Ethereum and Polygon.